package other.process;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.stat.StatUtils;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.graph.build.feature.FeatureGraphGenerator;
import org.geotools.graph.build.line.LineStringGraphGenerator;
import org.geotools.graph.path.DijkstraShortestPathFinder;
import org.geotools.graph.path.Path;
import org.geotools.graph.structure.Edge;
import org.geotools.graph.structure.Graph;
import org.geotools.graph.structure.Node;
import org.geotools.graph.traverse.standard.DijkstraIterator;
import org.junit.Assert;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.index.strtree.STRtree;
import org.opengis.feature.simple.SimpleFeature;
import utilityTools.ShpReaderUtil;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;

/**
 * @Author jlj
 * @Date 2022-05-06 16:31
 * @Description 网络分析服务
 * @Modified
 */
@Slf4j
public class NetworkAnalysis {

    //定义一个枚举，表示最短路径的方式(0-长度最短；1-花费时间最短)
    private double valueVariance = 0.0, valueMean = 0.0, valueMin = 0.0, valueMax = 0.0, valueSum = 0.0;

    public void analysisShortestRoute(String filterField, String oidBegin, String oidDestination, int mode) throws Exception {
        mode = 0;//先采用长度最短方式计算
        log.info("查询从{}到{}的最短路径：", oidBegin, oidDestination);
        String errorShpPath="E:\\5.自研产品研发_2022\\路网分析服务\\长安区\\长安区_交通路网.shp";
        String pathShapefile = "E:\\5.自研产品研发_2022\\路网分析服务\\Sample01\\RoadChangan\\road02.shp";
        SimpleFeatureSource sfs = ShpReaderUtil.getShpfileSource(new File(errorShpPath), "GBK");
        //Assert.assertTrue("路网数据不合法，不能是Multi-Polyline类型，请重新处理！", this.isRoadRightful(sfs));
        SimpleFeatureCollection features = sfs.getFeatures();
        //创建graph数据结构
        Graph graph_src = buildGraph(features);
        System.out.println("边的数量：" + graph_src.getEdges().size());
        //定义权重
        DijkstraIterator.EdgeWeighter weight = new DijkstraIterator.EdgeWeighter() {
            @Override
            public double getWeight(Edge edge) {
                //这个方法返回的值就是权重，这里使用的最简单的线的长度(如果有路况、限速等信息，可以做的更复杂一些)
                SimpleFeature feature = (SimpleFeature) edge.getObject();
                Geometry geometry = (Geometry) feature.getDefaultGeometry();
                return geometry.getLength();
                //Boolean isOneway = Boolean.valueOf(feature.getAttribute("oneway").toString());
                //double edgeLength = geometry.getLength();
                //return edgeLength;
            }
        };
        //获取最短路径的起止点（这两个没问题）
        Node nodeBegin = this.getNodeAOfFeature(graph_src, oidBegin, filterField);
        Node nodeDestination = this.getNodeAOfFeature(graph_src, oidDestination, filterField);
        //初始化查找器
        DijkstraShortestPathFinder finderShortestPath = new DijkstraShortestPathFinder(graph_src, nodeBegin, weight);
        finderShortestPath.calculate();
        Path pathShortest = finderShortestPath.getPath(nodeDestination);
        //理论上来说，一个路网中所有的边都应该是能够联通的，只不过是成本(权重的大小不同而已)，不能出现从一个边找不到另一个边的情况
        Assert.assertNotNull("最短路径分析失败，从[" + oidBegin + "]到[" + oidDestination + "]没有联通的路径，" +
                "请重新检查路网数据的连通性！", pathShortest);
        //还原Path对应的原始路网信息(or返回Path对应的一系列路径的GeoJson对象)
        List<String> oids_Feature = new ArrayList<>();
        for (Object objEdge : pathShortest.getEdges()) {
            SimpleFeature perEdge = (SimpleFeature) ((Edge) objEdge).getObject();
            oids_Feature.add(perEdge.getAttribute(filterField).toString());
        }
        //根据实际情况返回路径对应的信息
        System.out.println(oids_Feature);
        System.out.println("执行完毕");
        sfs.getDataStore().dispose();
    }

    /**
     * 判断路网数据是否合法[这个问题没解决，目前只能通过求解过程或者Path结果判断原始路网是否合法]
     *
     * @param sfs 原始路网对象
     * @return 注意：后期如果有其他的限制条件，也可以加在这里
     * 暂时不能使用，因为shp类型不能存储详细的几何类型，只能到Multi-点线面级别
     */
    private boolean isRoadRightful(SimpleFeatureSource sfs) {
        String nameGeometryType = sfs.getSchema().getGeometryDescriptor().getType().getName().toString();
        log.info("路网数据的类型是：{}", nameGeometryType);
        //包含multi属于非法数据
        return !StringUtils.containsIgnoreCase(nameGeometryType, "Multi");
    }

    public void analysis(String filterField, String oidBegin, String oidDestination) {

    }

    /**
     * 在路网Graph中获取OID对应的边的起始点Node对象
     *
     * @param graph_src    路网Graph对象
     * @param gidOfFeature 指定路网边的OID
     * @param filterField  OID列的名称
     * @return 指定路网边对象在graph中的边的NodeA对象
     */
    private Node getNodeAOfFeature(Graph graph_src, String gidOfFeature, String filterField) {
        //默认都从gid对应路线的起点开始进行路径搜索
        Optional<Edge> edgeBeginOptional = graph_src.getEdges().stream().filter(new Predicate<Edge>() {
            @Override
            public boolean test(Edge edge) {
                return StringUtils.equals(((SimpleFeature) edge.getObject()).getAttribute(filterField).toString(),
                        gidOfFeature);
                //return ((SimpleFeature) edge.getObject()).getAttribute("gid") == gidOfFeature;
            }
        }).findFirst();
        //我断定condition是true，如果不是true则会抛出异常并且附带异常信息
        Assert.assertTrue("无法获取路网中的[OID:" + gidOfFeature + "]起/终点位置，请检查参数是否合法!", edgeBeginOptional.isPresent());
        Edge edgeBegin = edgeBeginOptional.get();
        return edgeBegin.getNodeA();
    }

    /**
     * 构建原始路网的Graph结构对象
     *
     * @param features
     * @return
     * @throws IOException
     */
    private Graph buildGraph(SimpleFeatureCollection features) throws IOException {
        LineStringGraphGenerator lineStringGen = new LineStringGraphGenerator();
        FeatureGraphGenerator featureGen = new FeatureGraphGenerator(lineStringGen);
        ArrayList<Double> arrayLength = new ArrayList<>();
        double[] srcLength = new double[features.size()];
        try (SimpleFeatureIterator sfIterator = features.features()) {
            while (sfIterator.hasNext()) {
                SimpleFeature simpleFeature = sfIterator.next();
                Geometry geometry = (Geometry) simpleFeature.getDefaultGeometry();
                //if (geometry.getNumGeometries() > 1) {
                //    System.out.println("错误数据：" + simpleFeature.getAttribute("OBJECTID"));
                //}
                //Assert.assertFalse("路网数据不合法，存在多体对象，请重新处理！", geometry.getNumGeometries() > 1);
                featureGen.add(simpleFeature);
                arrayLength.add(geometry.getLength());
            }
        } catch (Exception error) {
            System.out.println("构造Graph对象失败：" + error.getLocalizedMessage());
        }
        double[] doubles = arrayLength.stream().mapToDouble(Double::doubleValue).toArray();
        //不能采用ZScore标准化，因为会出现负值
        //valueVariance = StatUtils.variance(doubles);
        //valueMean = StatUtils.mean(doubles);
        //应该采用[0,1]标准化，这样才能正确计算权重
        valueMin = StatUtils.min(doubles);
        valueMax = StatUtils.max(doubles);
        valueSum = StatUtils.sum(doubles);
        //StatUtils.variance();//求方差（标准差=方差的开平方）
        //z=(x-μ)/σ【z=（具体值-平均值）÷标准差】
        Graph graph = featureGen.getGraph();
        log.info("路网Graph构建完成！");
        return graph;
        //另外一种方式（但不能直接用于SimpleFeatureCollection对象，需要变更）
        //final LineGraphGenerator generator =new BasicLineGraphGenerator();
        //features.accepts(new FeatureVisitor() {
        //    @Override
        //    public void visit(Feature feature) {
        //        generator.add(feature);
        //    }
        //},null);
        //return generator.getGraph();
    }

    public void analysis2() throws Exception {
        //(1)构造路径查询原始信息
        String oidBegin = "33", oidDestination = "54";
        //途径58,61,44,63,65
        //停靠点：(12450082.070,3728547.334),(12498130.914,3695880.365),(12521746.720,3729211.082),(12445069.428,3671668.007)
        //(2)获取原始道路数据集参数
        String pathShapefile = "E:\\5.自研产品研发_2022\\路网分析服务\\Sample01\\CustomizeData\\road.shp";
        SimpleFeatureSource sfs = ShpReaderUtil.getShpfileSource(new File(pathShapefile), "utf-8");
        SimpleFeatureCollection features = sfs.getFeatures();//测试数据原始共53241条记录
        //(2.2)获取途径参数
        Point midWay1 = new GeometryFactory().createPoint(new Coordinate(12498130.914, 3695880.365));
        Point midWay2 = new GeometryFactory().createPoint(new Coordinate(12521746.720, 3729211.082));
        HashMap<Integer, String> nearestEdge_Midway = this.getNearestEdge_Loop(sfs, new Point[]{midWay1, midWay2});
        //(3)构建GeoTools的Graph对象
        Graph roadGraph = this.buildGraph(features);
        //(4)构建起止点信息
        Node nodeBegin = this.getNodeAOfFeature(roadGraph, oidBegin, "OBJECTID");
        Node nodeDestination = this.getNodeAOfFeature(roadGraph, oidDestination, "OBJECTID");
        //(5)构建途经点信息
        Node[] nodesMidway = new Node[nearestEdge_Midway.size()];
        for (int idx = 0; idx < nearestEdge_Midway.size(); idx++) {
            Node node = this.getNodeAOfFeature(roadGraph, nearestEdge_Midway.get(idx), "OBJECTID");
            nodesMidway[idx] = node;
        }
        //(6)定义权重(需要考虑途经点、限速、等级等权重以什么方式添加？!)
        //DijkstraIterator.NodeWeighter nodeWeighter=new DijkstraIterator.NodeWeighter() {
        //    @Override
        //    public double getWeight(Node nodeGraph, Edge edge, Edge edge1) {
        //        Arrays.stream(nodesMidway).forEach(new Consumer<Node>() {
        //            @Override
        //            public void accept(Node nodeMidway) {
        //                if (nodeGraph.getID()==nodeMidway.getID()) {
        //                    nodeGraph.setVisited(true);
        //                }
        //            }
        //        });
        //        return 0;
        //    }
        //};
        DijkstraIterator.EdgeWeighter edgeWeighter = new DijkstraIterator.EdgeWeighter() {
            @Override
            public double getWeight(Edge edge) {
                //这个方法返回的值就是权重，这里使用的最简单的线的长度
                // 如果有路况、限速等信息，可以做的更复杂一些
                //首先添加‘长度’权重
                SimpleFeature feature = (SimpleFeature) edge.getObject();
                Geometry geometry = (Geometry) feature.getDefaultGeometry();
                double timeCostSource = Double.parseDouble(feature.getAttribute("Time").toString());//长度作为原始cost
                double lengthCostSource = geometry.getLength();
                //double zScore = (geometry.getLength() - valueMean) / Math.sqrt(valueVariance);//本身就有负值，所以不行
                //下面这种方式，计算出来的结果不对，为什么呢？
                //double zScore = (geometry.getLength() - valueMin) / (valueMax - valueMin);//*Math.pow(10,-15);
                //double zScore = geometry.getLength();
                //log.warn("归一化后的权重值：{}", zScore);
                //return Double.parseDouble(feature.getAttribute("Time").toString());
                double cost = 0.0d;
                if (feature.getAttribute("OBJECTID").equals("61")) {
                    cost = Double.NEGATIVE_INFINITY;//lengthCostSource-lengthCostSource;
                    log.warn("计划途径61这个边,需要降低花费，权重（负无穷大）：{}", cost);
                } else if (feature.getAttribute("OBJECTID").equals("37")) {
                    cost = Double.POSITIVE_INFINITY;//该条路不可通行，设置其权重为‘正无穷大’
                    log.warn("设置37这个边为不可通行，需要提升花费,权重（正无穷大）：{}", cost);
                } else {
                    cost = lengthCostSource;
                }
                return cost;
            }
        };
        //(7)构造查询器并进行最短路径计算
        DijkstraShortestPathFinder finderShortestPath = new DijkstraShortestPathFinder(roadGraph, nodeBegin,
                edgeWeighter);
        finderShortestPath.calculate();
        Path pathShortest = finderShortestPath.getPath(nodeDestination);
        //还原Path对应的原始路网信息(or返回Path对应的一系列路径的GeoJson对象)
        List<String> oids_Feature = new ArrayList<>();
        for (Object objEdge : pathShortest.getEdges()) {
            SimpleFeature perEdge = (SimpleFeature) ((Edge) objEdge).getObject();
            oids_Feature.add(perEdge.getAttribute("OBJECTID").toString());
        }
        //最终把gid返回给调用方，通过gid获取原始路网中的记录，进而展示路径结果；或者将整个结果返回为一个GeoJson对象？会不会太大呢？
        System.out.println(oids_Feature.toString());
        System.out.println("执行完毕");//目前流程得到的结果不对！
        log.info("如果按照最短路程，则结果应该是：53→37→35");
        log.info("如果按照最短时间，则结果应该是：51→32");
    }

    private String getNearestEdge_RTree(SimpleFeatureSource featureSource, Point pntSearch) throws IOException {
        STRtree rtree = new STRtree();
        SimpleFeatureIterator sft = featureSource.getFeatures().features();
        while (sft.hasNext()) {
            SimpleFeature perFeature = sft.next();
            //以线为中心，做一个半径为500m的缓冲区
            rtree.insert(((Geometry) perFeature.getDefaultGeometry()).buffer(500).getEnvelopeInternal(),
                    ((Geometry) perFeature.getDefaultGeometry()).buffer(500));
        }
        //这里可以理解为以500作为缓冲半径行程的几何对象的空间索引，然后查询;而空间索引可以多次使用;
        rtree.build();
        List query = rtree.query(pntSearch.getEnvelopeInternal());
        for (Object result : query) {
            System.out.println(result);//得到的结果仅仅是一个几何对象
            //因此这里
        }
        System.out.println(query.size());
        //参考老Server的空间查询操作试一下
        return "01";
    }

    /**
     * 返回的是最近边的OID值，需要进一步改造？！
     *
     * @param featureSource 原始数据集
     * @param midWayPoints  途经点(必须和数据集坐标系一致)
     * @return
     * @throws IOException
     */
    private HashMap<Integer, String> getNearestEdge_Loop(SimpleFeatureSource featureSource, Point[] midWayPoints)
            throws IOException {
        int cntMidway = midWayPoints.length;
        HashMap<Integer, HashMap<String, Double>> midwayDistance = new HashMap<>();
        for (int i = 0; i < cntMidway; i++) {
            HashMap<String, Double> hm_I = new HashMap<>();
            midwayDistance.put(i, hm_I);
        }
        SimpleFeatureIterator iterator = featureSource.getFeatures().features();
        //这样的话每次都要循环所有的要素，效率很低
        while (iterator.hasNext()) {
            SimpleFeature currentFeature = iterator.next();
            //一次计算所有途经点的信息
            for (int idx = 0; idx < cntMidway; idx++) {
                double distance = midWayPoints[idx].distance(((Geometry) currentFeature.getDefaultGeometry()));
                String oid = currentFeature.getAttribute("OBJECTID").toString();
                midwayDistance.get(idx).put(oid, distance);
            }
        }
        HashMap<Integer, String> resultMidway = new HashMap<>();
        for (int idx = 0; idx < cntMidway; idx++) {
            Optional<Map.Entry<String, Double>> minDisEdge =
                    midwayDistance.get(idx).entrySet().stream().min(Map.Entry.comparingByValue());
            //每一个途经点，都对应离他最近的边的OID值
            resultMidway.put(idx, minDisEdge.get().getKey());
        }
        return resultMidway;
    }
}
